import base64
import hashlib
import json
import math
import threading
import time
import uuid
from datetime import datetime
from enum import Enum

import dicttoxml
import jsonrpcclient
import xmltodict
from dateutil import parser
from requests import delete as delete
from requests import exceptions as http_exception
from requests import get as get
from requests import post as post
from requests import put as put
from requests.packages import urllib3

from server.pao_python.pao.data import process_db
from server.pao_python.pao.remote import JsonRpc2Error
from server.pao_python.pao.service.data.mongo_db import (
    C, F, MongoService, N, as_date)

from ...service.buss_pub.bill_manage import (BillManageService, OperationType,
                                             Status, TypeId)
from ...service.common import (SerialNumberType, delete_data, find_data,
                               get_condition, get_current_user_id, get_info,
                               get_serial_number, get_user_id_or_false,
                               insert_data, update_data)
from ...service.mongo_bill_service import MongoBillFilter
from .emi_callcenter_error_code import EmiCallCenterErrorCodeEnum as ErrorCode

urllib3.disable_warnings()


class EmiCallCenterEnum(Enum):
    # 请求链接地址
    # 获取主账号信息
    MAIN_ACCOUNT_INFO_URL = '/%s/Accounts/%s/AccountInfo'
    # 创建企业用户地址
    CREATE_USER_URL = '/%s/SubAccounts/%s/Enterprises/createUser'
    # 删除企业用户地址
    DROP_USER_URL = '/%s/SubAccounts/%s/Enterprises/dropUser'
    # 签入地址
    SIGN_IN_URL = '/%s/SubAccounts/%s/CallCenter/signIn'
    # 签出地址
    SIGN_OFF_URL = '/%s/SubAccounts/%s/CallCenter/signOff'
    # 改变坐席模式地址
    CHANGE_MODE_URL = '/%s/SubAccounts/%s/CallCenter/changeMode'
    # 改变坐席状态地址
    CHANGE_STATUS_URL = '/%s/SubAccounts/%s/CallCenter/changeStatus'
    # 坐席呼出地址
    CALLOUT_URL = '/%s/SubAccounts/%s/CallCenter/callOut'
    # 挂断电话地址
    CALL_CANCEL_URL = '/%s/SubAccounts/%s/CallCenter/callCancel'
    # 获取通话语音文件下载地址的地址
    GET_CALL_RECORD_URL = '/%s/Accounts/%s/Applications/callRecordUrl'
    # 创建技能组
    CREATE_GROUP_URL = '/%s/SubAccounts/%s/Enterprises/createGroup'
    # 添加技能组用户
    ADD_GROUP_USER_URL = '/%s/SubAccounts/%s/Enterprises/addGroupUser'
    # 获取通话详情
    CALL_DETAIL_URL = '/%s/Accounts/%s/Applications/callDetail'
    # 更新企业用户绑定号码
    UPDATE_USER_PHONE = '/%s/SubAccounts/%s/Enterprises/updateUserPhone'
    # 账号类型
    # 主账号
    ACCOUNT_TYPE_MAIN = 'mainAccount'
    # 子账号
    ACCOUNT_TYPE_SUB = 'subAccount'
    # 通话类型


class HttpErrorEnum(Enum):
    '''http 错误码'''
    ERROR_TIMEOUT = (35030, '请求超时')
    ERROR_CONNECT_TIMEOUT = (35031, '连接超时')
    ERROR_READ_TIMEOUT = (35032, '读取超时')
    ERROR_CONNECTION = (35033, '连接错误')
    ERROR_HTTP = (35034, 'HTTP错误')
    ERROR_TOO_MANY_REDIRECTS = (35035, '过多的重定向错误')
    ERROR_REQUEST = (35036, '请求错误')


# http请求函数
def http_request(method='get', url='', headers: dict = None, data: dict = None, timeout=10, **kwargs):
    request = globals()[method]
    if headers:
        kwargs['headers'] = headers
    if data:
        kwargs['data'] = data
    kwargs['timeout'] = timeout
    try:
        res = request(url, **kwargs)
        if not res.ok:
            raise res.raise_for_status()
        return res
    except http_exception.ConnectTimeout:
        # 连接超时
        raise JsonRpc2Error(HttpErrorEnum.ERROR_CONNECT_TIMEOUT.value[0], HttpErrorEnum.ERROR_CONNECT_TIMEOUT.value[1])
    except http_exception.ReadTimeout:
        # 读超时
        raise JsonRpc2Error(HttpErrorEnum.ERROR_READ_TIMEOUT.value[0], HttpErrorEnum.ERROR_READ_TIMEOUT.value[1])
    except http_exception.Timeout:
        # 请求超时
        raise JsonRpc2Error(HttpErrorEnum.ERROR_TIMEOUT.value[0], HttpErrorEnum.ERROR_TIMEOUT.value[1])
    except http_exception.ConnectionError:
        # 连接错误
        raise JsonRpc2Error(HttpErrorEnum.ERROR_CONNECTION.value[0], HttpErrorEnum.ERROR_CONNECTION.value[1])
    except http_exception.HTTPError:
        # http错误
        raise JsonRpc2Error(HttpErrorEnum.ERROR_HTTP.value[0], HttpErrorEnum.ERROR_HTTP.value[1])
    except http_exception.TooManyRedirects:
        # 过多重定向
        raise JsonRpc2Error(HttpErrorEnum.ERROR_TOO_MANY_REDIRECTS.value[0], HttpErrorEnum.ERROR_TOO_MANY_REDIRECTS.value[1])
    except http_exception.RequestException:
        # 请求错误
        raise JsonRpc2Error(HttpErrorEnum.ERROR_REQUEST.value[0], HttpErrorEnum.ERROR_REQUEST.value[1])


class EmiHttpService():
    def __init__(self, emi_call_center_data):
        self.SoftwareVersion = emi_call_center_data['SoftwareVersion']
        self.appid = emi_call_center_data['appid']
        self.mainAccount = emi_call_center_data['mainAccount']
        self.subAccount = emi_call_center_data['subAccount']
        self.host = emi_call_center_data['host']
        self.group_id = emi_call_center_data['gid']

    def base64_encode(self, str):
        return base64.b64encode(str.encode('utf-8')).decode("utf-8")

    def md5_encrypted(self, str):
        md5 = hashlib.md5()
        md5.update(str.encode(encoding='utf-8'))
        return md5.hexdigest()

    def remote_request(self, method='post', url: str = '', headers: dict = None, data: dict = None, is_json=True, **kwargs):
        if is_json:
            data = json.dumps(data)
        res = http_request(method, url=url, headers=headers, data=data, verify=False, **kwargs).json()
        resp_code = res['resp']['respCode']
        if resp_code == 0:
            return res
        else:
            error = ErrorCode['E_'+str(resp_code)].value
            raise JsonRpc2Error(error[0], error[1])

    def generate_headers_url(self, url_name: EmiCallCenterEnum, account_type: EmiCallCenterEnum = EmiCallCenterEnum.ACCOUNT_TYPE_SUB):
        account = self.__dict__[account_type.value]
        return self.__prepare(account, url_name.value)

    def __prepare(self, account, url_path: str):
        # 生成统一时间错
        timestamp = time.strftime("%Y%m%d%H%M%S", time.localtime())
        # 使用账号sid和时间戳，生成base64编码的认证请求头
        Authorization = self.base64_encode(account['sid'] + ':' + timestamp)
        # 使用账号sid、token和时间戳，生成md5加密的签名
        sign = self.md5_encrypted(account['sid'] + account['token'] + timestamp).upper()
        path = url_path % (self.SoftwareVersion, account['sid']) + '?sig=' + sign
        url = self.host + path
        headers = {
            'Content-Type': 'application/json;charset=UTF-8',
            'Accept': 'application/json',
            'Authorization': Authorization
        }
        return {
            'url': url,
            'headers': headers
        }
    
    def _main_account_info(self):
        url_headers = self.generate_headers_url(EmiCallCenterEnum.MAIN_ACCOUNT_INFO_URL, EmiCallCenterEnum.ACCOUNT_TYPE_MAIN)
        return self.remote_request(url=url_headers['url'], headers=url_headers['headers'])
        
    def _create_user(self, work_number, phone, display_name, password, number):
        url_headers = self.generate_headers_url(EmiCallCenterEnum.CREATE_USER_URL)
        create_user = {
            'appId': self.appid,
            'workNumber': work_number,
            'phone': phone,
            'displayName': display_name,
            'password': password,
            'number': number
        }
        data = {
            'createUser': create_user
        }
        return self.remote_request(url=url_headers['url'], headers=url_headers['headers'], data=data)
         
    def _drop_user(self, work_number):
        url_headers = self.generate_headers_url(EmiCallCenterEnum.DROP_USER_URL)
        drop_user = {
            'appId': self.appid,
            'workNumber': work_number
        }
        data = {
            'dropUser': drop_user
        }
        return self.remote_request(url=url_headers['url'], headers=url_headers['headers'], data=data)
    
    def _create_group(self, group_name):
        url_headers = self.generate_headers_url(EmiCallCenterEnum.CREATE_GROUP_URL)
        create_group = {
            'appId': self.appid,
            'groupName': group_name
        }
        data = {
            'createGroup': create_group
        }
        return self.remote_request(url=url_headers['url'], headers=url_headers['headers'], data=data)
    
    def _add_group_user(self, group_id, work_number):
        url_headers = self.generate_headers_url(EmiCallCenterEnum.ADD_GROUP_USER_URL)
        add_group_user = {
            'appId': self.appid,
            'gid': group_id,
            'workNumber': work_number
        }
        data = {
            'addGroupUser': add_group_user
        }
        return self.remote_request(url=url_headers['url'], headers=url_headers['headers'], data=data)
    
    def _sign_in(self, work_number, device_number=None):
        url_headers = self.generate_headers_url(EmiCallCenterEnum.SIGN_IN_URL)
        sign_in = {
            'appId': self.appid,
            'workNumber': work_number
        }
        if device_number:
            sign_in['type'] = '1'
            sign_in['deviceNumber'] = device_number
        print(sign_in)
        data = {
            'signIn': sign_in
        }
        res = self.remote_request(url=url_headers['url'], headers=url_headers['headers'], data=data)
        return res
    
    def _sign_off(self, work_number):
        url_headers = self.generate_headers_url(EmiCallCenterEnum.SIGN_OFF_URL)
        sign_off = {
            'appId': self.appid,
            'workNumber': work_number
        }
        data = {
            'signOff': sign_off
        }
        return self.remote_request(url=url_headers['url'], headers=url_headers['headers'], data=data)
    
    def _change_mode(self, work_number, mode, device_number):
        url_headers = self.generate_headers_url(EmiCallCenterEnum.CHANGE_MODE_URL)
        change_mode = {
            'appId': self.appid,
            'workNumber': work_number,
            'mode': mode,
            'deviceNumber': device_number
        }
        data = {
            'changeMode': change_mode
        }
        return self.remote_request(url=url_headers['url'], headers=url_headers['headers'], data=data)

    def _change_status(self, work_number, status):
        url_headers = self.generate_headers_url(EmiCallCenterEnum.CHANGE_STATUS_URL)
        change_status = {
            'appId': self.appid,
            'workNumber': work_number,
            'status': status
        }
        data = {
            'changeStatus': change_status
        }
        return self.remote_request(url=url_headers['url'], headers=url_headers['headers'], data=data)
    
    def _callout(self, work_number, to):
        url_headers = self.generate_headers_url(EmiCallCenterEnum.CALLOUT_URL)
        callout = {
            'appId': self.appid,
            'workNumber': work_number,
            'to': to
        }
        data = {
            'callOut': callout
        }
        return self.remote_request(url=url_headers['url'], headers=url_headers['headers'], data=data)
    
    def _call_cancel(self, work_number, call_id):
        url_headers = self.generate_headers_url(EmiCallCenterEnum.CALL_CANCEL_URL)
        call_cancel = {
            'appId': self.appid,
            'workNumber': work_number,
            'callId': call_id
        }
        data = {
            'callCancel': call_cancel
        }
        return self.remote_request(url=url_headers['url'], headers=url_headers['headers'], data=data)
    
    def _get_call_record_url(self, call_id):
        url_headers = self.generate_headers_url(EmiCallCenterEnum.GET_CALL_RECORD_URL, EmiCallCenterEnum.ACCOUNT_TYPE_MAIN)
        call_record_url = {
            'appId': self.appid,
            'callId': call_id
        }
        data = {
            'callRecordUrl': call_record_url
        }
        return self.remote_request(url=url_headers['url'], headers=url_headers['headers'], data=data)

    def _call_detail(self, call_id):
        url_headers = self.generate_headers_url(EmiCallCenterEnum.CALL_DETAIL_URL, EmiCallCenterEnum.ACCOUNT_TYPE_MAIN)
        call_detail = {
            'appId': self.appid,
            'callId': call_id
        }
        data = {
            'callDetail': call_detail
        }
        return self.remote_request(url=url_headers['url'], headers=url_headers['headers'], data=data)

    def _update_user_phone(self, work_number, phone):
        url_headers = self.generate_headers_url(EmiCallCenterEnum.UPDATE_USER_PHONE)
        update_user_phone = {
            'appId': self.appid,
            'workNumber': work_number,
            'phone': phone
        }
        data = {
            'updateUserPhone': update_user_phone
        }
        return self.remote_request(url=url_headers['url'], headers=url_headers['headers'], data=data)


# 数据库操作函数
# 通过电话查找定位设备-老人
def find_device_elder_by_phone(caller, bill_manage_server):
    _filter = MongoBillFilter()
    _filter.match_bill(C('sim_no') == caller).inner_join_bill('PT_User', 'user_id', 'id', 'user').project({'_id': 0})
    device_user = bill_manage_server.query(_filter, 'PT_Device')
    if len(device_user) > 0:
        elder_device = device_user[0]['user']
        elder_device['device_type'] = device_user[0]['device_type']
        elder_device['imei'] = device_user[0]['imei']
        return elder_device
    return None


# 通过电话查找老人
def find_elder_by_call(call, bill_manage_server):
    _filter = MongoBillFilter()
    _filter.match_bill((C('personnel_info.telephone') == call)).project({'_id': 0})
    res = bill_manage_server.query(_filter, 'PT_User')
    if len(res) > 0:
        return res[0]
    return None


# 通过坐席号查找坐席人员
def find_cs_by_number(number, bill_manage_server):
    _filter_cs = MongoBillFilter()
    _filter_cs.match_bill((C('number') == number)).inner_join_bill('PT_User', 'user_id', 'id', 'user').project({'_id': 0})
    cs_list = bill_manage_server.query(_filter_cs, 'emi_role')
    if len(cs_list) > 0:
        return cs_list[0]
    else:
        return None


# 根据user_id查找坐席人员
def find_cs_by_user_id(user_id, bill_manage_server):
    _filter_cs = MongoBillFilter()
    _filter_cs.match_bill((C('user_id') == user_id)).inner_join_bill('PT_User', 'user_id', 'id', 'user').project({'_id': 0})
    cs_list = bill_manage_server.query(_filter_cs, 'emi_role')
    cs = None
    if len(cs_list) > 0:
        cs = cs_list[0]
    return cs


# 根据user_id查询坐席人员关联机构
def find_org_by_user_id(user_id, bill_manage_server):
    _filter_org = MongoBillFilter()
    _filter_org.match(C('principal_account_id') == user_id).inner_join_bill('PT_User', 'role_of_account_id', 'id', 'org').project({'_id': 0})
    role_list = bill_manage_server.query(_filter_org, 'PT_Set_Role')
    if len(role_list) > 0:
        return role_list[0]['org']
    return None


# 根据条件查找通话记录
def query_call_record(condition, page, count, get_value, page_query):
    keys = ['create_time', 'caller', 'type', 'cs_name', 'id', 'emergency', 'user_id', 'elder_card_number', 'elder_name']
    values = get_value(condition, keys)
    _filter = MongoBillFilter()
    _filter.match_bill((C('caller') == values['caller']) & (C('id') == values['id'])
                    & (C('user_id') == values['user_id']) & (C('cs_name').like(values['cs_name']))
                    & (C('type') == values['type']) & (C('elder.card_number') == values['elder_card_number'])
                    & (C('elder.name').like(values['elder_name']))
                    & (C('emergency') == values['emergency']) & (C('user_id') == values['user_id'])).project({'_id': 0}).sort({'create_time': -1})
    if 'create_time' in condition:
        create_time = condition['create_time']
        begin_time = get_mongo_date(create_time)
        _filter.match_bill(C('create_time') >= begin_time)
    call_list = page_query(_filter, 'PT_Call_Record', page, count)
    return call_list


# 根据call_id查询通话记录
def find_call_record_by_call_id(call_id, bill_manage_server):
    _filter = MongoBillFilter()
    _filter.match_bill(C('call_id') == call_id).project({'_id': 0})
    call_records = bill_manage_server.query(_filter, 'PT_Call_Record')
    if len(call_records) > 0:
        return call_records[0]
    return None


# 更新或者修改通话记录
def update_call_record(call_record, bill_manage_server):
    if 'id' in call_record:
        bill_id = bill_manage_server.add_bill(OperationType.update.value, TypeId.emiCallCenter.value, call_record, 'PT_Call_Record')
    else:
        bill_id = bill_manage_server.add_bill(OperationType.add.value, TypeId.emiCallCenter.value, call_record, 'PT_Call_Record')
    if bill_id:
        return True
    return False


class EmiCallCenterService(EmiHttpService, MongoService):
    def __init__(self, ecmi_call_center_data, db_addr, db_port, db_name, db_user, db_pwd, inital_password, session):
        super().__init__(ecmi_call_center_data)
        self.db_addr = db_addr
        self.db_port = db_port
        self.db_name = db_name
        self.db_user = db_user
        self.db_pwd = db_pwd
        self.session = session
        self.bill_manage_server = BillManageService(self.db_addr, self.db_port, self.db_name, self.db_user, self.db_pwd, inital_password, session)
    
    # 备用接口，暂时不用
    def create_cs(self, work_number, phone, display_name, password, number, user_id):
        self._create_user(work_number, phone, display_name, password, number)
        self._add_group_user(self.group_id, work_number)
        cs = {
            'id': str(uuid.uuid1()),
            'work_number': work_number,
            'phone': phone,
            'display_name': display_name,
            'password': password,
            'number': number,
            'user_id': user_id
        }
        res = 'Fail'
        
        def process_func(db):
            nonlocal res
            insert_data(db, 'PT_CS', cs)
            res = 'Success'
        process_db(self.db_addr, self.db_port, self.db_name, process_func, self.db_user, self.db_pwd)
        return res
    
    # 签入
    def sign_in(self):
        user_id = get_current_user_id(self.session)
        cs = cache.get_cs_from_cache_by_session(self.session)
        if not cs:
            cs = find_cs_by_user_id(user_id, self.bill_manage_server)
            cache.set_cs_to_cache(cs)
        work_number = cs['worker_no']
        # 接入话机的情况下，携带话机号码签入
        print(cs)
        if ('sig_no' in cs) and (cs['sig_no'] is not None):
            self._sign_in(work_number, cs['sig_no'])
        # 耳机方式签入
        else:
            self._sign_in(work_number)
        self.session["is_login_emi"] = True
        return 'Success'

    # 获取坐席人员所在机构的地址
    def get_cs_org_location(self):
        org = find_org_by_user_id(get_current_user_id(self.session), self.bill_manage_server)
        if org:
            return {
                'name': org['name'],
                'lng': org['organization_info']['lon'],
                'lat': org['organization_info']['lat']
            }
        return None

    # 签出
    def sign_off(self):
        cs = cache.get_cs_from_cache_by_session(self.session)
        work_number = cs['worker_no']
        # cache.del_cs_from_cache_by_session(self.session)
        if 'mobile' in cs:
            self._update_user_phone(work_number, cs['user']['personnel_info']['telephone'])
        self._sign_off(work_number)
        self.session["is_login_emi"] = False
        return 'Success'
    
    # 呼出电话
    def callout(self, to):
        if '-' in to:
            to = to.replace('-', '')
        work_number = cache.get_cs_from_cache_by_session(self.session)['worker_no']
        elder = find_elder_by_call(to, self.bill_manage_server)
        if elder:
            cache.set_elder_to_cache(elder)
        call_id = self._callout(work_number, to)['resp']['callOut']['callId']
        return {
            'call_id': call_id,
            'elder': elder
        }
    
    # 挂断电话
    def call_cancel(self, call_id):
        work_number = cache.get_cs_from_cache_by_session(self.session)['worker_no']
        return self._call_cancel(work_number, call_id)
    
    # 获取通话下载链接
    def get_call_record_url(self, call_id):
        return self._get_call_record_url(call_id)
    
    # 根据坐席获取呼入电话信息，坐席10秒不接听电话，呼叫会被转移到另外坐席，需要跟新通话信息
    def has_call_in(self):
        cs = cache.get_cs_from_cache_by_session(self.session)
        user_id = get_current_user_id(self.session)
        number = cs['number']
        calls = cache.get_call_in_dict()
        call = None
        call_in_cache = None
        # print(calls)
        for call_info in calls.values():
            if call_info['user_id'] == user_id:
                call_in_cache = call_info
                break
        if call_in_cache:
            # 暂停2秒，防止因轮询过快，通话状态不能重新由1变回0，导致的通话丢失，应对易米bug
            # time.sleep(2)
            call_details = self._call_detail(call_in_cache['call_id'])['resp']['callDetail']
            start_time = call_details['establishTime']
            stop_time = call_details['hangupTime']
            duration = call_details['duration']
            call_status = call_details['status']
            call_id = call_in_cache['call_id']
            is_unconnected = cache.handle_emi_bug(call_id, call_status)
            if 'SubDetails' in call_details:
                call_sub_details = call_details['SubDetails']
                if len(call_sub_details) > 0:
                    # 获取最新通话信息
                    latest_call = call_sub_details.pop()
                    called = latest_call['called']
                    # 取得最新的坐席号
                    now_number = None
                    # called可能会是云总机号码02566695189，不是坐席号码，需要剔除获取前一个
                    if not (called.startswith('0') and len(called) > 8):
                        now_number = called
                    else:
                        now_number = call_sub_details.pop()['called']
                    # 跟当前用户的坐席号比对
                    if number == now_number:
                        call = call_in_cache
                    else:
                        if called.startswith('1') and len(called) == 11:
                            # 切换到值班模式，called会是手机号码
                            cs = cache.get_cs_from_cache_by_mobile(called)
                        else:
                            # 跟当前用户坐席号不同，用坐席号查询用户，跟新缓存中的电话信息
                            cs = cache.get_cs_from_cache_by_number(now_number)
                        call_in_cache['number'] = cs['number']
                        call_in_cache['work_no'] = cs['worker_no']
                        call_in_cache['user_id'] = cs['user']['id']
                        call_in_cache['cs_name'] = cs['user']['name']
            else:
                if not(len(call_in_cache['called']) == 11):
                    call = call_in_cache
            # 当通话结束或者呼叫挂机将信息插入数据库，并清空缓存
            # 长时间不接电话也会返回1状态，但是保持呼叫，挂断不接听也会返回1，易米云通的问题,通过比对连续五次的状态确认是否为1
            if (call_status == '1' and is_unconnected) or call_status == '3':
                call_in_cache['type'] = 'in'
                if call_status == '3':
                    call_in_cache['start_time'] = get_mongo_date(start_time)
                    call_in_cache['stop_time'] = get_mongo_date(stop_time)
                    call_in_cache['duration'] = duration
                flag = update_call_record(call_in_cache, self.bill_manage_server)
                if flag:
                    cache.delete_call_from_cache(call_in_cache['call_type'], call_in_cache['call_id'])
                    call = None
        return call
    
    # 获取所有呼入电话信息
    def get_all_call_in(self):
        return list(cache.get_call_in_dict().values())
    
    # 查询通话记录列表
    def get_call_record_list(self, condition, page=None, count=None):
        call_list = query_call_record(condition, page, count, self.get_value, self.page_query)
        for call in call_list['result']:
            if 'duration' in call and call['duration'] != '0':
                try:
                    res = self._get_call_record_url(call['call_id'])
                    call['call_record_url'] = res['resp']['callRecordUrl']['url']
                except JsonRpc2Error as ex:
                    if ex.code == '602102':
                        print('通话录音失效或者通话不存在: ' + call['call_id'])
                    else:
                        print(ex.message)
        return call_list
    
    # 修改通话记录
    def update_one_call_record(self, call_record):
        # 限定只可以修改备注信息
        record = {
            'id': call_record['id'],
            'note': call_record['note']
        }
        res = 'Fail'
        flag = update_call_record(record, self.bill_manage_server)
        if flag:
            res = 'Success'
        return res
    
    # 考虑删除
    # 查询紧急呼叫记录
    def get_emergency_call_record_list(self, condition, page=None, count=None):
        keys = ['id']
        values = self.get_value(condition, keys)
        _filter = MongoBillFilter()
        _filter.match_bill((C('emergency') == 'yes') & (C('id') == values['id'])).project({'_id': 0})
        return self.page_query(_filter, 'PT_Call_Record', page, count)

    # 改变坐席模式
    def change_mode(self, mode, phone):
        # mode:0：为固定坐席  1：为值班坐席,当更改回固定坐席需要重新签出，签入
        # 根据传入的电话号码，转移呼叫，需要更新用户绑定的电话号码
        res = 'Fail'
        cs = cache.get_cs_from_cache_by_session(self.session)
        work_no = cs['worker_no']
        if mode == '1':
            # 修改易米云通后台绑定的电话号码，两个号码一致才可以转接
            self._update_user_phone(work_no, phone)
            self._change_mode(work_no, mode, phone)
            # 更新值班手机号码
            cs['mobile'] = phone
            res = 'Success'
        elif mode == '0':
            # 切换回固话模式，将绑定的电话号码修改为用户联系电话，以防其他坐席用同一个手机切换到值班模式
            self._update_user_phone(work_no, cs['user']['personnel_info']['telephone'])
            self._sign_off(work_no)
            self._sign_in(work_no)
            res = 'Success'
        return res

    # 实时为通话添加备注
    def add_call_note(self, call_type_code, call_id, note):
        res = 'Fail'
        call = cache.get_call_from_cache(call_type_code, call_id)
        if call:
            call['note'] = note
            res = 'Success'
        else:
            call = find_call_record_by_call_id(call_id, self.bill_manage_server)
            call['note'] = note
            flag = update_call_record(call, self.bill_manage_server)
            if flag:
                res = 'Success'
        return res


class DeviceTypeEnum(Enum):
    MI_GOU = 'mi_gou'
    XIAO_YI = 'xiao_yi'
    
    @staticmethod
    def get_type_by_name(name):
        for type in DeviceTypeEnum:
            if type.value == name:
                return type


class DeviceGateWayUrl(Enum):
    MI_GOU_GET_LOCATION = 'migou.get_device_location'
    XIAO_YI_GET_LOCATION = 'xiaoyi.get_locations'


class DeviceLocation:
    @staticmethod
    def get_device_location(type_name: str, imei: str, device_gateway_host):
        type = DeviceTypeEnum.get_type_by_name(type_name)
        res = None
        if type == DeviceTypeEnum.MI_GOU:
            imei = '80000' + imei
            res = DeviceLocation.get_mi_gou_location(device_gateway_host, imei)
        elif type == DeviceTypeEnum.XIAO_YI:
            res = DeviceLocation.get_xiao_yi_location(device_gateway_host, imei)
        return res
    
    @staticmethod
    def get_xiao_yi_location(device_gateway_host, imei: str):
        location = DeviceLocation.json_rpc_request(device_gateway_host, DeviceGateWayUrl.XIAO_YI_GET_LOCATION.value, imei)['data']['datas'][0]
        res = DeviceLocation.wgs84tobd09(location['longitude'], location['latitude'])
        address = DeviceLocation.lng_lat_to_address(res['x'], res['y'])
        return {
            'name': address['name'],
            'address': address['formatted_address'],
            'lat': res['y'],
            'lng': res['x'],
            'time': DeviceLocation.format_timestamp(location['createDate']/1000)
        }
        
    @staticmethod
    def get_mi_gou_location(device_gateway_host, imei: str):
        location = DeviceLocation.json_rpc_request(device_gateway_host, DeviceGateWayUrl.MI_GOU_GET_LOCATION.value, imei)['data']
        res = DeviceLocation.gcj02tobd09(location['lng'], location['lat'])
        address = DeviceLocation.lng_lat_to_address(res['x'], res['y'])
        return {
            'name': address['name'],
            'address': address['formatted_address'],
            'lat': res['y'],
            'lng': res['x'],
            'time': DeviceLocation.format_timestamp(location['time'])
        }
    
    @staticmethod
    def json_rpc_request(host: str, method: str, *args):
        res = jsonrpcclient.request(host, method, *args)
        return res.data.result
    
    # gps坐标转百度坐标
    @staticmethod
    def wgs84tobd09(lng, lat):
        url = 'http://api.map.baidu.com/ag/coord/convert?from=0&to=4&x=%s&y=%s' % (lng, lat)
        res = http_request(url=url).json()
        res['x'] = float(base64.b64decode(res['x']).decode("utf-8"))
        res['y'] = float(base64.b64decode(res['y']).decode("utf-8"))
        return res
    
    # 高德、谷歌坐标系专转百度坐标
    @staticmethod
    def gcj02tobd09(lng, lat):
        X_PI = math.pi * 3000.0 / 180.0
        z = math.sqrt(lng * lng + lat * lat) + 0.00002 * math.sin(lat * X_PI)
        theta = math.atan2(lat, lng) + 0.000003 * math.cos(lng * X_PI)
        lng = z * math.cos(theta) + 0.0065
        lat = z * math.sin(theta) + 0.006
        return {
            'x': lng,
            'y': lat
        }

    @staticmethod
    def format_timestamp(timestamp):
        # timestamp单位为秒
        timestamp = timestamp
        timeArray = time.localtime(timestamp)
        return time.strftime("%Y-%m-%d %H:%M:%S", timeArray)

    # 根据坐标查询地址
    @staticmethod
    def lng_lat_to_address(lng, lat):
        url = 'http://api.map.baidu.com/geocoder/v2/?callback=renderReverse&location=%s,%s&output=json&pois=1&ak=jEmXEmo4L2GkmC1Ops1gvFGm75ALGDji' % (lat, lng)
        res = http_request(url=url, headers={'Accept': 'text/xml'}).content.decode('utf-8')
        res_json = json.loads(res[res.find('(')+1:-1])['result']
        if len(res_json['pois']) > 0:
            name = res_json['pois'][0]['name']
        else:
            name = ''

        real_time_location = {
            "formatted_address": res_json['formatted_address'],
            'name': name
        }
        return real_time_location

    # 根据地址查询坐标
    @staticmethod
    def address_to_lng_lat(address):
        url = 'http://api.map.baidu.com/geocoder/v2/?output=json&ak=jEmXEmo4L2GkmC1Ops1gvFGm75ALGDji&address=%s' % address
        res = http_request(url=url).json()
        if res['status'] == 0:
            return {
                'lng': res['result']['location']['lng'],
                'lat': res['result']['location']['lat'],
                'address': address
            }
        return None


# 回调处理静态类
class HandleEmiCallback:
    # 处理易米呼叫中心呼叫请求回调
    @staticmethod
    def handle_call_request_callback(data, bill_manage_server, device_gateway_host):
        call_info = xmltodict.parse(data)['request']
        number = call_info['number']
        # 查询坐席人员信息
        cs = cache.get_cs_from_cache_by_number(number)
        if not cs:
            cs = find_cs_by_number(number, bill_manage_server)
        elder = None
        # 根据呼出电话查询长者信息
        if call_info['callType'] != '5':
            elder = cache.get_elder_from_cache(call_info['called'])
        # 根据呼入电话查询长者信息
        else:
            device_elder = find_device_elder_by_phone(call_info['caller'], bill_manage_server)
            if device_elder:
                elder = device_elder
            else:
                elder = find_elder_by_call(call_info['caller'], bill_manage_server)
        call_in_cache = {
            'call_id': call_info['callId'],
            'pickup': False,
            'create_time': get_mongo_date(),
            'work_no': call_info['workNumber'],
            'number': call_info['number'],
            'caller': call_info['caller'],
            'call_type': call_info['callType'],
            'called': call_info['called'],
            'emergency': 'no'
        }
        call_in_cache['user_id'] = cs['user_id']
        call_in_cache['cs_name'] = cs['user']['name']
        if elder:
            call_in_cache['elder'] = {
                'pic': '',
                'elder_id': elder['id'],
                'name': elder['name'],
                'sex': elder['personnel_info']['sex'],
                'birth': elder['personnel_info']['date_birth'],
                'card_number': elder['personnel_info']['id_card']
            }
            address = None
            if 'address' in elder['personnel_info']:
                if len(elder['personnel_info']['address']) > 0 and (not elder['personnel_info']['address'].isspace()):
                    address = elder['personnel_info']['address']
                    call_in_cache['elder']['address'] = address
            if 'device_type' in elder:
                device_type = elder['device_type']
                imei = elder['imei']
                call_in_cache['elder']['location'] = DeviceLocation.get_device_location(device_type, imei, device_gateway_host)
                call_in_cache['emergency'] = 'yes'
            else:
                if address and DeviceLocation.address_to_lng_lat(address):
                    call_in_cache['elder']['location'] = DeviceLocation.address_to_lng_lat(address)
        cache.set_call_to_cache(call_info['callType'], call_in_cache)
        return get_return()

    # 处理易米呼叫中心呼叫建立回调
    @staticmethod
    def handle_call_establish_callback(data):
        call_info = xmltodict.parse(data)['request']
        cache.update_call_pickup_state(call_info['callType'], call_info['callId'])
        print(cache.get_call_in_dict())
        return get_return()

    # 处理易米呼叫中心呼叫结束回调
    @staticmethod
    def handle_call_hangup_callback(data, bill_manage_server):
        call_info = xmltodict.parse(data)['request']
        call_type = call_info['callType']
        call_state = call_info['state']
        call_in_cache = cache.get_call_from_cache(call_info['callType'], call_info['callId'])
        if call_in_cache:
            if call_state == '0' or (call_type != '5' and call_state == '1'):
                call_in_cache['type'] = CallTypeEnum.get_type_by_code(call_in_cache['call_type'])
                if call_state == '0':
                    call_in_cache['start_time'] = get_mongo_date(call_info['startTime'])
                    call_in_cache['stop_time'] = get_mongo_date(call_info['stopTime'])
                    call_in_cache['duration'] = call_info['duration']
                flag = update_call_record(call_in_cache, bill_manage_server)
                if flag:
                    cache.delete_call_from_cache(call_in_cache['call_type'], call_in_cache['call_id'])
        return get_return()


# 呼叫中心回调统一结果返回
def get_return():
    return dicttoxml.dicttoxml({'retcode': 0, 'reason': 'success'}, custom_root='response', attr_type=False).decode('utf-8')


# 将日期字符串转换为mongo日期
def get_mongo_date(strdate=None):
    if strdate is None:
        now = time.strftime("%Y%m%d%H%M%S", time.localtime())
        return parser.parse(now)
    else:
        dateStr = str(datetime.strptime(strdate, '%Y%m%d%H%M%S'))
        return parser.parse(dateStr)


# 电话类型枚举类
class CallTypeEnum(Enum):
    TWO_WAY_CALLBACK = ('0', 'tow_way_callback', 'out')
    DIRECT_DIAL = ('1', 'direct_dial', 'out')
    VOICE_VERIFICATION = ('2', 'voice_verification', 'out')
    VOICE_NOTIFICATION = ('3', 'voice_notification', 'out')
    MULTIPARTY_CALL = ('4', 'multiparty_call', 'out')
    CALL_IN = ('5', 'call_in', 'in')
    BATCH_CALL_OUT = ('7', 'batch_call_out', 'out')
    
    @staticmethod
    def get_call_type_by_code(code):
        for type in CallTypeEnum:
            if type.value[0] == code:
                return type.value[1]
            
    @staticmethod
    def get_type_by_code(code):
        for type in CallTypeEnum:
            if type.value[0] == code:
                return type.value[2]


# 缓存易米呼叫中心回调数据
class EmiCallCache:
    
    # 呼叫记录锁
    __call_lock = threading.RLock()
    # 坐席、长者锁
    __elder_cs_lock = threading.RLock()
    # 通话缓存字典
    __call_dict = {}
    # 长者信息缓存字典
    __elder = {}
    # 坐席缓存字典
    __cs = {}
    # 状态错误次数缓存
    __error_time = {}
    
    def __init__(self):
        for type in CallTypeEnum:
            self.__call_dict[type.value[1]] = {}

    def set_call_to_cache(self, call_type_code: str, call_info: dict):
        with self.__call_lock:
            call_type = CallTypeEnum.get_call_type_by_code(call_type_code)
            self.__call_dict[call_type][call_info['call_id']] = call_info

    def get_call_from_cache(self, call_type_code: str, call_id: str):
        call_type = CallTypeEnum.get_call_type_by_code(call_type_code)
        calls = self.__call_dict[call_type]
        if call_id in calls:
            return calls[call_id]
        return None

    def delete_call_from_cache(self, call_type_code: str, call_id: str):
        with self.__call_lock:
            call_type = CallTypeEnum.get_call_type_by_code(call_type_code)
            if call_id in self.__call_dict[call_type]:
                del self.__call_dict[call_type][call_id]
    
    def update_call_pickup_state(self, call_type_code: str, call_id: str):
        with self.__call_lock:
            call_type = CallTypeEnum.get_call_type_by_code(call_type_code)
            self.__call_dict[call_type][call_id]['pickup'] = True
    
    def get_call_dict_by_type(self, call_type_code: str):
        call_type = CallTypeEnum.get_call_type_by_code(call_type_code)
        return self.__call_dict[call_type]
    
    def get_call_in_dict(self):
        return self.get_call_dict_by_type(CallTypeEnum.CALL_IN.value[0])
    
    # 将呼出的长者信息放入缓存
    def set_elder_to_cache(self, elder):
        with self.__elder_cs_lock:
            self.__elder[elder['personnel_info']['telephone']] = elder
    
    # 将坐席人员根据坐席号-坐席人员信息的形式放进缓存
    def set_cs_to_cache(self, cs):
        with self.__elder_cs_lock:
            self.__cs[cs['user_id']] = cs
    
    # 根据坐席号获取坐席人员信息
    def get_cs_from_cache_by_number(self, number):
        css = self.__cs
        for cs in css.values():
            if cs['number'] == number:
                return cs
        return None
    
    # 根据user_id获取坐席人员信息
    def get_cs_from_cache_by_session(self, session):
        css = self.__cs
        user_id = get_current_user_id(session)
        if user_id in css:
            return css[user_id]
        return None

    # 删除坐席
    def del_cs_from_cache_by_session(self, session):
        user_id = get_current_user_id(session)
        with self.__elder_cs_lock:
            del self.__cs[user_id]

    # 根据手机号码查询坐席人员信息
    def get_cs_from_cache_by_mobile(self, mobile):
        css = self.__cs
        for cs in css.values():
            if mobile == cs['mobile']:
                return cs
        return None

    # 呼出电话匹配的老人信息，呼叫请求建立后，删除老人信息
    def get_elder_from_cache(self, phone):
        with self.__elder_cs_lock:
            elders = self.__elder
            if phone in elders:
                elder = elders[phone]
                del self.__elder[phone]
                return elder
            else:
                return None

    # 处理易米云通通话详情偶尔返回'1'的问题，电话呼叫状态，却返回呼叫失败的状态
    # 连续返回五次状态'1'，判断通话已经挂断
    def handle_emi_bug(self, call_id, status):
        if status in ['0', '1']:
            if status == '1':
                if call_id in self.__error_time:
                    self.__error_time[call_id] = self.__error_time[call_id] + 1
                    if self.__error_time[call_id] == 5:
                        del self.__error_time[call_id]
                        return True
                else:
                    self.__error_time[call_id] = 1
            else:
                if call_id in self.__error_time:
                    self.__error_time[call_id] = 0
        return False


cache = EmiCallCache()
